from collections import defaultdict
from sapi import *
from color import *
import json
from puzzlecell import PuzzleCell

# from puzzle import Puzzle
from grid import Grid, Cell
from sapi import RResult, unmarshal
import tkinter as tk
from msglabel import MessageLabel
import math
import puzzle


class TipTool:
    def __init__(self, puzzle: "puzzle.Puzzle"):
        self.puzzle = puzzle
        self.grid: Grid = puzzle.grid
        self.tip_num: int = 0
        self.tip_results: list[str] = []

    def tip_init(self):
        self.tip_num = 0
        self.tip_results = []

    def tip(self, strategy: str, msgLabel: MessageLabel):
        if self.tip_results:
            print("tip_num", self.tip_num, self.tip_results)
            result = self.tip_results[self.tip_num]
            self.tip_num = (self.tip_num + 1) % len(self.tip_results)
            self.tip_handle_result(result, msgLabel)
            return
        else:
            self.tip_init()
            resp = rpc_step(strategy, self.puzzle.grid.encode())
            error = resp["error"]
            if error:
                msgLabel.configure(text=error)
                return None

            self.tip_results = json.loads(resp["result"])
            result = self.tip_results[self.tip_num]
            self.tip_num = (self.tip_num + 1) % len(self.tip_results)
            self.tip_handle_result(result, msgLabel)

    def tip_handle_result(self, result: str, msgLabel: MessageLabel):
        r = json.loads(result)

        self.tip_result_message(r, msgLabel)

        rresult = unmarshal(r)
        if not rresult:
            return

        self.puzzle.turnoffTippedWidgets()
        self.tip_block(rresult)
        self.tip_help_values(rresult)
        self.tip_help_chains(rresult)
        self.tip_help_cands(rresult)
        self.tip_help_chosen(rresult)
        self.tip_removed(rresult)

    def tip_result_message(self, result: dict, msgLabel: MessageLabel):
        s = ""
        for k, v in result.items():
            if v:
                s += f"{k}:{v}\n"
        s = s.replace("'", '"')
        msgLabel.configure(text=s)

    def tip_block(self, rresult: RResult):
        blockColors = defaultdict(list)

        for i, hs in enumerate(rresult.helpHouses):
            for h in hs:
                if h.kind == "R":
                    cs = self.grid.rows[h.num]
                elif h.kind == "C":
                    cs = self.grid.cols[h.num]
                else:
                    cs = self.grid.boxes[h.num]
                # print("cs:", cs)
                for c in cs:
                    bc = self.puzzle.baseCells[c.rowNum][c.colNum]
                    color = HelpCellsColors[i]
                    blockColors[bc.block].append(color)
                    # color = HelpCellsColors[i]
                    # self.canvas.itemconfig(bc.block, fill=color, state="normal")
                    # self.hiddenToNormals.append(bc.block)

        for i, hc in enumerate(rresult.helpCells):
            for c in hc:
                bc = self.puzzle.baseCells[c.rowNum][c.colNum]
                color = HelpCellsColors[i]
                blockColors[bc.block].append(color)
                #
                # self.canvas.itemconfig(bc.block, fill=color, state="normal")
                # self.hiddenToNormals.append(bc.block)

        for b, cs in blockColors.items():
            color = cs[0]
            for c in cs[1:]:
                color = blend_colors(color, c, 0.5)
            self.puzzle.canvas.itemconfig(b, fill=color, state="normal")
            self.puzzle.tippedWidgets.append(b)

    def tip_help_cands(self, rresult: RResult):

        for i, cs in enumerate(rresult.helpCands):
            for c in cs:
                for value in c.cands:
                    circle = self.puzzle.baseCells[c.rowNum][c.colNum].candCircles[
                        value - 1
                    ]
                    color = HelpCandsColors[i]
                    self.puzzle.canvas.itemconfig(circle, fill=color, state="normal")
                    self.puzzle.tippedWidgets.append(circle)

    def tip_help_values(self, rresult: RResult):
        # print("rresult.helpvalues", rresult.helpvalues)
        for i, hv in enumerate(rresult.helpValues):
            print("helpvalues", hv)
            for v in hv:
                # print("helpvalues", v)
                circle = self.puzzle.baseCells[v.rowNum][v.colNum].valueCircle
                color = HelpCandsColors[i]
                self.puzzle.canvas.itemconfig(circle, fill=color, state="normal")
                self.puzzle.tippedWidgets.append(circle)

    def tip_help_chosen(self, rresult: RResult):
        if rresult.chosen:
            c: RValue = rresult.chosen[0]
            # print("choose", c)
            circle = self.puzzle.baseCells[c.rowNum][c.colNum].candCircles[c.value - 1]
            self.puzzle.canvas.itemconfig(circle, fill=chosenColor, state="normal")

            block = self.puzzle.baseCells[c.rowNum][c.colNum].block
            if rresult.strategyName == "Guess":
                self.puzzle.canvas.itemconfig(block, fill=guessBg, state="normal")
            else:
                self.puzzle.canvas.itemconfig(block, fill=chosenBg, state="normal")

            self.puzzle.tippedWidgets.append(circle)
            self.puzzle.tippedWidgets.append(block)

    def tip_help_chains(self, rresult: RResult):
        for chain in rresult.helpChains:
            self.drawChain(chain)

    def tip_removed(self, rresult: RResult):
        for c in rresult.removed:
            for value in c.cands:
                circle = self.puzzle.baseCells[c.rowNum][c.colNum].candCircles[
                    value - 1
                ]
                self.puzzle.canvas.itemconfig(circle, fill=removeColor, state="normal")
                self.puzzle.tippedWidgets.append(circle)

    def drawChain(self, chain: RChain):
        plotter = ChainPlotter(self.puzzle, chain)
        plotter.calc_node_info()
        plotter.plot()
        plotter.raise_widgets()


class ChainPlotter:
    def __init__(self, puzzle: "puzzle.Puzzle", chain: RChain) -> None:
        self.puzzle = puzzle
        self.grid = puzzle.grid
        self.nodes = chain.rnodes
        self.links = chain.links
        self.center = (0, 0)

    def get_center(self):
        sx = 0
        sy = 0
        for node in self.node_info:
            x, y = node["point"]
            sx += x
            sy += y
        n = len(self.node_info)
        self.center = (sx / n, sy / n)

    def calc_node_info(self):
        node_info = []  # 存储每个节点的类型（single/multi）、坐标信息
        for i, node in enumerate(self.nodes):
            cells = [
                self.puzzle.baseCells[rcell.rowNum][rcell.colNum]
                for rcell in node.rcells
            ]

            if len(cells) == 1:
                # 单格子节点：使用候选数字坐标
                cell = cells[0]
                cand_index = node.cand - 1
                x = cell.x0 + (cand_index % 3 + 0.5) * (PuzzleCell.cellSize / 3)
                y = cell.y0 + (cand_index // 3 + 0.5) * (PuzzleCell.cellSize / 3)
                node_info.append(
                    {
                        "type": "single",
                        "circle": cell.candCircles[cand_index],  # 候选圆圈引用
                        "point": (x, y),
                        "cells": cells,
                    }
                )
            else:
                # 多格子节点：计算包围矩形坐标（同一行/列）
                x0 = min(c.x0 for c in cells) + (cand_index % 3) * (
                    PuzzleCell.cellSize / 3
                )
                x1 = max(c.x0 for c in cells) + (cand_index % 3 + 1) * (
                    PuzzleCell.cellSize / 3
                )
                y0 = min(c.y0 for c in cells) + (cand_index // 3) * (
                    PuzzleCell.cellSize / 3
                )
                y1 = max(c.y0 for c in cells) + (cand_index // 3 + 1) * (
                    PuzzleCell.cellSize / 3
                )

                # 矩形边界坐标（基于单元格位置）
                # x0 = cells[0].x0  # 最左列单元格的x起点
                # x1 = cells[-1].x0 + PuzzleCell.cellSize  # 最右列单元格的x终点
                # y0 = cells[0].y0  # 最上行单元格的y起点
                # y1 = cells[-1].y0 + PuzzleCell.cellSize  # 最下行单元格的y终点

                # 计算各边中点（用于连线）
                mid_left = (x0, (y0 + y1) / 2)  # 左边中点
                mid_right = (x1, (y0 + y1) / 2)  # 右边中点
                mid_top = ((x0 + x1) / 2, y0)  # 上边中点
                mid_bottom = ((x0 + x1) / 2, y1)  # 下边中点

                # 绘制矩形节点
                color = linkRedColor if i % 2 == 0 else linkGreenColor
                rect = self.puzzle.canvas.create_rectangle(
                    x0,
                    y0,
                    x1,
                    y1,
                    outline=color,  # 自定义矩形边框颜色
                    fill="",  # 自定义矩形填充颜色
                    width=2,
                )
                self.puzzle.tippedWidgets.append(rect)  # 记录到高亮部件

                node_info.append(
                    {
                        "type": "multi",
                        "rect": rect,
                        "bounds": (x0, y0, x1, y1),
                        "mid_points": {
                            "left": mid_left,
                            "right": mid_right,
                            "top": mid_top,
                            "bottom": mid_bottom,
                        },
                        "point": ((x0 + x1) / 2, (y0 + y1) / 2),
                        "cells": cells,
                    }
                )
        self.node_info = node_info

        self.get_center()

    def plot(self):
        node_info = self.node_info
        links = self.links

        for i in range(len(node_info) - 1):
            if self.two_nodes_in_same_cell(i):
                self.plot_cell_link(i)
                continue

            prev_node = node_info[i]
            curr_node = node_info[i + 1]
            conn_type = links[i]

            prev_x, prev_y = prev_node["point"]
            curr_x, curr_y = curr_node["point"]

            if prev_node["type"] == "single":
                start = prev_x, prev_y
            else:
                alpha = math.atan2(prev_y - curr_y, prev_x - curr_x)
                if -math.pi * 3 / 4 <= alpha < -math.pi / 4:
                    start = prev_node["mid_points"]["bottom"]
                elif -math.pi / 4 <= alpha <= math.pi / 4:
                    start = prev_node["mid_points"]["left"]
                elif math.pi / 4 <= alpha <= math.pi * 3 / 4:
                    start = prev_node["mid_points"]["top"]
                else:
                    start = prev_node["mid_points"]["right"]

            if curr_node["type"] == "single":
                end = curr_x, curr_y
            else:
                alpha = math.atan2(curr_y - prev_y, curr_x - prev_x)
                if -math.pi * 3 / 4 <= alpha < -math.pi / 4:
                    end = curr_node["mid_points"]["bottom"]
                elif -math.pi / 4 <= alpha <= math.pi / 4:
                    end = curr_node["mid_points"]["left"]
                elif math.pi / 4 <= alpha <= math.pi * 3 / 4:
                    end = curr_node["mid_points"]["top"]
                else:
                    end = curr_node["mid_points"]["right"]

            # 绘制连接线（保持原强弱颜色逻辑）
            color = strongColor if conn_type == "=" else weakColor
            dash = () if conn_type == "=" else (5, 1)
            line = self.puzzle.canvas.create_line(
                start[0],
                start[1],
                end[0],  # Now properly defined
                end[1],  # Now properly defined
                fill=color,
                width=2,
                dash=dash,
                smooth=True,
                arrow=tk.LAST,
                arrowshape=(10, 12, 3),
            )
            self.puzzle.tippedWidgets.append(line)

    def raise_widgets(self):
        nodes = self.nodes
        for i, node in enumerate(nodes):
            for rcell in node.rcells:
                cell: PuzzleCell = self.puzzle.baseCells[rcell.rowNum][rcell.colNum]
                cand_index = node.cand - 1
                circle = cell.candCircles[cand_index]
                color = linkRedColor if i % 2 == 0 else linkGreenColor
                self.puzzle.canvas.itemconfig(circle, fill=color, state="normal")
                self.puzzle.canvas.tag_raise(circle)
                self.puzzle.canvas.tag_raise(cell.textCands[cand_index])
                self.puzzle.tippedWidgets.append(circle)

    def two_nodes_in_same_cell(self, i: int):
        prev_node = self.nodes[i]
        curr_node = self.nodes[i + 1]
        return (
            len(prev_node.rcells) == 1
            and len(curr_node.rcells) == 1
            and prev_node.rcells[0].rowNum == curr_node.rcells[0].rowNum
            and prev_node.rcells[0].colNum == curr_node.rcells[0].colNum
        )

    def plot_cell_link(self, i: int):
        """
        绘制从start点到end点的半圆连接线
        半圆的直径就是start点到end点的线段
        """
        prev_node = self.node_info[i]
        curr_node = self.node_info[i + 1]
        conn_type = self.links[i]

        start = prev_node["point"]
        end = curr_node["point"]

        # 获取前一点 A 和后一点 B 的坐标
        A = None
        B = None
        if i > 0:
            A = self.node_info[i - 1]["point"]
        if i + 2 < len(self.node_info):
            B = self.node_info[i + 2]["point"]

        # 计算中点和向量
        mid_x = (start[0] + end[0]) / 2
        mid_y = (start[1] + end[1]) / 2
        dx = end[0] - start[0]
        dy = end[1] - start[1]

        # 计算垂直向量并归一化
        norm = math.sqrt(dx**2 + dy**2)
        if norm == 0:
            raise ValueError("Start and end points are the same.")
        perp_dx = -dy / norm
        perp_dy = dx / norm

        # 判断 A 和 B 的位置
        def is_left_of_line(point):
            """判断点是否在 start-end 直线的左侧"""
            return (dx * (point[1] - start[1]) - dy * (point[0] - start[0])) > 0

        both_left = False
        both_right = False
        one_side_one = False  # 新增变量：表示 A 和 B 是否分别在两侧
        if A and B:
            both_left = is_left_of_line(A) and is_left_of_line(B)
            both_right = not is_left_of_line(A) and not is_left_of_line(B)
            one_side_one = is_left_of_line(A) != is_left_of_line(B)  # A 和 B 位于两侧
        elif A:
            both_left = is_left_of_line(A)
            both_right = not is_left_of_line(A)
        elif B:
            both_left = is_left_of_line(B)
            both_right = not is_left_of_line(B)

        # 根据 A 和 B 的位置调整控制点方向
        def dc(norm):
            r = norm * 3 / PuzzleCell.cellSize
            if r < 1.1:
                return 3
            elif r < 1.5:
                return 1.5
            elif r < 2.1:
                return 0.8
            else:
                return 0.4

        control_distance = dc(norm) * norm
        if both_left:
            control_point_x = mid_x - perp_dx * control_distance
            control_point_y = mid_y - perp_dy * control_distance
        elif both_right:
            control_point_x = mid_x + perp_dx * control_distance
            control_point_y = mid_y + perp_dy * control_distance
        elif one_side_one:  # 新增逻辑：A 和 B 位于两侧
            # 计算 A-start 和 end-B 的斜率
            slope_A_start = (
                (start[1] - A[1]) / (start[0] - A[0])
                if start[0] != A[0]
                else float("inf")
            )
            slope_end_B = (
                (B[1] - end[1]) / (B[0] - end[0]) if B[0] != end[0] else float("inf")
            )

            # 计算控制点在左右两边时的距离
            left_control_x = mid_x - perp_dx * control_distance
            left_control_y = mid_y - perp_dy * control_distance
            right_control_x = mid_x + perp_dx * control_distance
            right_control_y = mid_y + perp_dy * control_distance

            # 计算左方向和右方向的距离
            dist_left = distance_point_to_line(
                (left_control_x, left_control_y), A, start
            ) + distance_point_to_line((left_control_x, left_control_y), end, B)
            dist_right = distance_point_to_line(
                (right_control_x, right_control_y), A, start
            ) + distance_point_to_line((right_control_x, right_control_y), end, B)

            # 选择距离较远的方向
            if dist_left >= dist_right:
                control_point_x = left_control_x
                control_point_y = left_control_y
            else:
                control_point_x = right_control_x
                control_point_y = right_control_y
        else:
            control_point_x = mid_x + perp_dx * control_distance
            control_point_y = mid_y + perp_dy * control_distance

        color = strongColor if conn_type == "=" else weakColor
        dash = () if conn_type == "=" else (5, 1)
        line = self.puzzle.canvas.create_line(
            [
                (start[0], start[1]),
                (control_point_x, control_point_y),
                (end[0], end[1]),  # Now properly defined
            ],  # Now properly defined
            fill=color,
            width=2,
            dash=dash,
            smooth=True,
            arrow=tk.LAST,
            arrowshape=(10, 12, 3),
        )
        self.puzzle.tippedWidgets.append(line)


def distance_point_to_line(point, line_start, line_end):
    """计算点到直线的距离"""
    x0, y0 = point
    x1, y1 = line_start
    x2, y2 = line_end
    numerator = abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1)
    denominator = math.sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2)
    return numerator / denominator
